package fem2.pre_and_post;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Writer;

import fem2.Debugger;
import fem2.Element;
import fem2.Mesh;
import fem2.Model;
import fem2.Node;
import fem2.NotImplementedError;

/**
 * class to write post-processing file for GID
 * 
 * @author hbui
 * 
 */
public class GidPost {

	private static final String GID_HEADER = "GiD Post Results File 1.0\n";
	private static final String POST_FILE_TYPE = ".post.res";
	private static final String MESH_POST_FILE_TYPE = ".post.msh";

	private int[][] GAUSS_POINT_MAPPING_FOR_QUAD_FROM_FEM2012_TO_GID = new int[][] { //
	{ 1 },//
//			{ 1, 3, 4, 2 }, //
			{ 1, 3, 4, 2 }, //
			{ 1, 7, 9, 3, 4, 8, 6, 2, 5 } //
	};

	/*
	 * TODO: there are inconsistent in Gauss point for triangle of hofem2011 and
	 * Gid Gauss point. check this. Current solution is to use GaussTriangleGid
	 */
	private int[][] GAUSS_POINT_MAPPING_FOR_TRI_FROM_FEM2012_TO_GID = new int[][] { { 1 },
			{ 1, 2, 3 }, { 1, 2, 3, 4, 5, 6 } };

	private int[][] GAUSS_POINT_MAPPING_FOR_HEX_FROM_FEM2012_TO_GID = new int[][] { { 1 },//
			{ 1, 5, 7, 3, 2, 6, 8, 4 } };

	private int[][] GAUSS_POINT_MAPPING_FOR_TET_FROM_FEM2012_TO_GID = new int[][] { { 1 },//
			{ 1, 2, 3, 4 }, { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } };

	private String projectDir;
	private String projectName;
	private String folderName = null;
	private String fileName = null;

	/**
	 * @param projectDir
	 *            the directory by which project reside
	 * @param projectName
	 *            full project name, must contain ".gid"
	 */
	public GidPost(String projectDir, String projectName) {
		if (projectName.length() < 4) {
			throw new Error("invalid project name");
		} else {
			int tmp = projectName.length() - 4;
			String type = projectName.substring(tmp);
			if (!type.equals(".gid")) {
				throw new Error("invalid project name");
			} else {
				this.projectName = projectName.substring(0, tmp);
				this.projectDir = projectDir;
				this.fileName = this.projectName;
			}
		}
	}

	/**
	 * specify gid project with different output file
	 * 
	 * @param projectDir
	 * @param projectName
	 * @param fileName
	 */
	public GidPost(String projectDir, String projectName, String fileName) {
		this(projectDir, projectName);
		this.folderName = fileName;
		this.fileName = fileName;
	}

	/**
	 * delete all post.res files in project directory
	 */
	public void clearAllPostFiles() {
		if (folderName == null) {
			String dir = projectDir + "/" + projectName + ".gid/";
			deleteFiles(dir, fileName, POST_FILE_TYPE);
			deleteFiles(dir, fileName, MESH_POST_FILE_TYPE);
		} else {
			String dir = projectDir + "/" + projectName + ".gid/" + folderName + "/";
			deleteFiles(dir, fileName, POST_FILE_TYPE);
			deleteFiles(dir, fileName, MESH_POST_FILE_TYPE);
		}
	}

	// /**
	// * mode all the post files to local folder folderName
	// *
	// * @param folderName
	// */
	// public void moveAllPostFiles(String folderName) {
	// String dir = projectDir + "/" + projectName + ".gid/";
	// String dest = dir + folderName + "/";
	// File file = new File(dest);
	// if (!file.exists()) {
	// if (!file.mkdir()) {
	// throw new Error("Create local folder failed: ");
	// }
	// }
	// moveFiles(dir, fileName, POST_FILE_TYPE, dest);
	// moveFiles(dir, fileName, MESH_POST_FILE_TYPE, dest);
	// }

	protected Writer createWriter(double timeStep) throws IOException {
		String dir = projectDir + "/" + projectName + ".gid/";

		String fn = createFileName(dir, timeStep, POST_FILE_TYPE);

		File file = new File(fn);
		Writer output = null;
		if (!file.exists()) {
			/*
			 * create new file
			 */
			output = new BufferedWriter(new FileWriter(file));
			/*
			 * write the header
			 */
			output.write(GID_HEADER);
		} else {
			/*
			 * open existing file to append
			 */
			output = new BufferedWriter(new FileWriter(file, true));
		}
		/*
		 * starting to write/append data
		 */
		return output;
	}

	/**
	 * create file name compatible to Gid
	 * 
	 * @param dir
	 * @param timeStep
	 * @param extension
	 * @return
	 * @throws Error
	 */
	private String createFileName(String dir, double timeStep, String extension) throws Error {
		String fn;
		if (folderName == null) {
			fn = dir + fileName + "_" + String.format("%.6f", timeStep) + extension;
		} else {
			/*
			 * create local folder if not exist
			 */
			String dest = dir + folderName + "/";
			File file = new File(dest);
			if (!file.exists()) {
				if (!file.mkdir()) {
					throw new Error("Create local folder failed: ");
				}
			}

			fn = dir + folderName + "/" + fileName + "_" + String.format("%.6f", timeStep)
					+ extension;
		}
		return fn;
	}

	/**
	 * get the Gauss point mapping into Gid
	 * 
	 * @param elementType
	 * @param ng
	 * @return
	 */
	protected int[] getGaussPointMapping(String elementType, int ng) {
		if (elementType.equals("Quadrilateral")) {
			int p = (int) Math.sqrt(ng);
			return GAUSS_POINT_MAPPING_FOR_QUAD_FROM_FEM2012_TO_GID[p - 1];
		} else if (elementType.equals("Triangle")) {
			int p;
			if (ng == 1) {
				p = 1;
			} else if (ng == 3) {
				p = 2;
			} else if (ng == 6) {
				p = 3;
			} else {
				throw new NotImplementedError();
			}
			return GAUSS_POINT_MAPPING_FOR_TRI_FROM_FEM2012_TO_GID[p - 1];
		} else if (elementType.equals("Hexahedra")) {
			int p = (int) Math.pow(ng, 1.0 / 3);
			if (p > 2) {
				throw new NotImplementedError();
			}
			return GAUSS_POINT_MAPPING_FOR_HEX_FROM_FEM2012_TO_GID[p - 1];
		} else if (elementType.equals("Tetrahedra")) {
			int p = 1;
			if (ng == 1) {
				p = 1;
			} else if (ng == 4) {
				p = 2;
			} else if (ng == 10) {
				p = 3;
			} else {
				throw new NotImplementedError();
			}
			return GAUSS_POINT_MAPPING_FOR_TET_FROM_FEM2012_TO_GID[p - 1];
		} else {
			throw new Error("not supported element type");
		}
	}

	/**
	 * delete files with particular extension and file name:
	 * directory/fileName_*.extension
	 * 
	 * @param directory
	 * @param fileName
	 * @param extension
	 */
	private void deleteFiles(String directory, String fileName, String extension) {
		FileFilter filter = new FileFilter(fileName, extension);
		File dir = new File(directory);

		String[] list = dir.list(filter);
		File file;
		if (list == null)
			return;

		for (int i = 0; i < list.length; i++) {
			file = new File(directory + list[i]);
			boolean isDeleted = file.delete();
			System.out.print(file);
			System.out.println("  deleted " + isDeleted);
		}
	}

	// /**
	// * move files with particular extension and file name:
	// * directory/fileName_*.extension
	// *
	// * @param directory
	// * @param fileName
	// * @param extension
	// */
	// private void moveFiles(String directory, String fileName, String
	// extension, String destination) {
	// FileFilter filter = new FileFilter(fileName, extension);
	// File dir = new File(directory);
	//
	// String[] list = dir.list(filter);
	// File file;
	// if (list == null)
	// return;
	//
	// for (int i = 0; i < list.length; i++) {
	// file = new File(directory + list[i]);
	// File dest = new File(destination, file.getName());
	// boolean isMoved = file.renameTo(dest);
	// System.out.print(file);
	// System.out.println("  moved " + isMoved);
	// }
	// }

	private class FileFilter implements FilenameFilter {

		private String fileName;;
		private String extension;

		public FileFilter(String fileName, String extension) {
			this.fileName = fileName;
			this.extension = extension;
		}

		public boolean accept(File dir, String name) {
			boolean cri1 = name.endsWith(extension);
			boolean cri2 = name.contains(fileName + "_");
			return cri1 && cri2;
		}
	}

	public void writeMesh(Model m, double timeStep) throws IOException {
		String dir = projectDir + "/" + projectName + ".gid/";

		String fn = createFileName(dir, timeStep, MESH_POST_FILE_TYPE);

		File file = new File(fn);
		Mesh mesh = m.getMesh();
		int dim = mesh.getDimension();
		if (file.exists()) {
			// /*
			// * delete mesh file
			// */
			// file.delete();
			/*
			 * skip delete to avoid write many times
			 */
			return;
		}
		/*
		 * create new file
		 */
		Writer w = new BufferedWriter(new FileWriter(file));
		/*
		 * write block for each element type
		 */
		boolean writeNode = true;
		for (String elemType : m.getElementTypes()) {
			w.write("MESH");
			w.write(" dimension " + dim);
			w.write(" ElemType " + elemType);
			w.write(" Nnode " + m.getElementNumNodes().get(elemType));
			w.write("\n");
			w.write("Coordinates\n");
			/*
			 * write the nodes for the first block
			 */
			if (writeNode) {
				int nn = mesh.countNodes();
				for (int i = 0; i < nn; i++) {
					Node node = mesh.getNode(i);
					w.write(node.getId() + "");
					double[] p = node.getPosition();
					for (int j = 0; j < dim; j++) {
						w.write("\t" + p[j]);
					}
					w.write("\n");
				}
				writeNode = false;
			}
			w.write("End Coordinates\n");
			w.write("Elements\n");
			/*
			 * write elements
			 */
			int ne = m.countElements();
			for (int i = 0; i < ne; i++) {
				Element e = m.getElement(i);
				if (e.getElemType().equals(elemType) && !e.isInactive()) {
					w.write(e.getId() + "");
					Node[] nodes = e.getMeshPart().getNodes();
					for (int j = 0; j < nodes.length; j++) {
						w.write("\t" + nodes[j].getId());
					}
					w.write("\n");
				}
			}
			w.write("End Elements\n");
		}

		w.close();

		Debugger.watch("Write mesh file completed");
	}

	/**
	 * write scalar nodal results to Gid
	 * 
	 * @param variableName
	 *            name of scalar variable
	 * @param values
	 *            must be of length nn equal to number of nodes
	 * @param timeStep
	 *            the time step which you want to write
	 * @throws IOException
	 */
	public void writeNodalScalarResult(String variableName, double[] values, double timeStep)
			throws IOException {
		Writer output = createWriter(timeStep);
		output.write("Result ");
		output.write('"' + variableName + '"' + " ");
		output.write('"' + "fem2012 analysis" + '"' + " ");
		output.write(timeStep + " ");
		output.write("Scalar ");
		output.write("OnNodes\n");
		output.write("Values\n");

		int nn = values.length;
		for (int i = 0; i < nn; i++) {
			output.write((i + 1) + " " + values[i] + "\n");
		}

		output.write("End Values\n");
		output.close();

		Debugger.watch("Write nodal scalar results ", variableName, " completed");
	}

	/**
	 * write vector nodal results to Gid
	 * 
	 * @param variableName
	 *            name of scalar variable
	 * @param values
	 *            2d array with length equal to number of nodes
	 * @param timeStep
	 *            the time step
	 * @throws IOException
	 */
	public void writeNodalVectorResult(String variableName, double[][] values, double timeStep)
			throws IOException {
		Writer output = createWriter(timeStep);
		output.write("Result ");
		output.write('"' + variableName + '"' + " ");
		output.write('"' + "fem2012 analysis" + '"' + " ");
		output.write(timeStep + " ");
		output.write("Vector ");
		output.write("OnNodes\n");
		output.write("Values\n");

		int nn = values.length;
		for (int i = 0; i < nn; i++) {
			double[] v = values[i];
			output.write((i + 1) + "");
			for (int j = 0; j < v.length; j++) {
				double d = v[j];
				output.write(" " + d);
			}
			output.write("\n");
		}

		output.write("End Values\n");
		output.close();

		Debugger.watch("Write nodal vector results", variableName, "completed");
	}

	/**
	 * write a block of Gauss point value for a specific elementType
	 * 
	 * @param variableName
	 * @param groupName
	 * @param elementType
	 * @param elementId
	 * @param values
	 * @param timeStep
	 * @throws IOException
	 */
	public void writeGausspointScalarResult(String variableName, String groupName,
			String elementType, int[] elementId, double[][] values, double timeStep)
			throws IOException {
		int ne = values.length;
		int ng = values[0].length;
		Writer output = createWriter(timeStep);
		/*
		 * write declaration for GaussPoints block
		 */
		output.write("GaussPoints ");
		output.write('"' + groupName + '"' + " ");
		output.write("ElemType " + elementType + "\n");
		output.write("Number Of Gauss Points: " + ng + "\n");
		output.write("Natural Coordinates: Internal\n");
		output.write("End GaussPoints\n");
		/*
		 * write block of result declaration
		 */
		output.write("Result ");
		output.write('"' + variableName + '"' + " ");
		output.write('"' + "fem2012 analysis" + '"' + " ");
		output.write(timeStep + " ");
		output.write("Scalar ");
		output.write("OnGaussPoints ");
		output.write('"' + groupName + '"' + "\n");
		output.write("Values\n");
		/*
		 * write data
		 */
		for (int i = 0; i < ne; i++) {
			output.write(elementId[i] + " ");
			int[] map = getGaussPointMapping(elementType, ng);
			for (int j = 0; j < ng; j++) {
				output.write(values[i][map[j] - 1] + "\n");
			}
		}
		/*
		 * end and close
		 */
		output.write("End Values\n");
		output.close();

		Debugger.watch("Write Gauss point scalar results", variableName, "group", groupName,
				"completed");
	}

}
